#pragma once

#include <google/protobuf/service.h>
#include "tinyrpc/base/task.h"
#include "tinyrpc/net/tcpserver.h"
#include "../redis/redisclient.h"
#include "tinyrpc/base/conf/config.h"
#include <functional>
#include <unordered_map>
#include <vector>
#include "tinyrpc/base/codec/lengthCodec.h"
#include "messagemanager.h"
#include "rpcService.h"
#include "../redis/RedisRegister.h"
#include "rpcserviceinfo.h"
#include "tinyrpc/base/logger/logger.h"
namespace TinyRPC{

class TinyRPCClient : public google::protobuf::RpcChannel, public TcpServer{

public:
    TinyRPCClient(int threadNum=1):TcpServer(nullptr, threadNum){
        m_codec = new LengthCodec();
        start();
        
    }

    void init(std::string filePath){
        m_config.init(filePath);
        std::string redis_ip = m_config.LookUp<std::string>("redis.ip");
        int redis_port = m_config.LookUp<int>("redis.port");
        m_redisRegister.init(redis_ip, redis_port);
        LOG_INFO<<"init redis";
        //getNextLoop()->runEvery(1, 10, Task(std::bind(&TinyRPCClient::co_updateServiceInfo, this), false));
        getNextLoop()->queueInLoop(Task(std::bind(&TinyRPCClient::co_discoverRPCService, this), true));
    }
    // 所有通过stub 代理对象调用rpc的方法， 都走到这里了，统一做rpc方法调用的数据序列化
    virtual void CallMethod(const google::protobuf::MethodDescriptor* method,
                          google::protobuf::RpcController* controller, const google::protobuf::Message* request,
                          google::protobuf::Message* response, google::protobuf::Closure* done);
    

    template<typename ParamType, typename ReturnType>
    ReturnType call(std::string service_name, std::string method_name, ParamType& param);

    template<typename ParamType, typename ReturnType>
    RPCRequestInfoPtr async_call(std::string service_name, std::string method_name, ParamType& param);


public:
    void registServiceStub(::google::protobuf::Service* service);
    void delRPCRequestInfo(RPCRequestInfoPtr infoPtr);

private:
    // 从remove获得消息
    void co_receiveResponseFromServer(RPCServicePtr service);
    void co_updateServiceInfo();
    RPCServicePtr getService(std::string service_name);

    void co_discoverRPCService();

private:
    
    RedisRegister m_redisRegister;
    std::unordered_map<std::string, RPCServiceInfoPtr> m_serviceMap;
    
    Config m_config;

    // 解码器
    AbstractCodec* m_codec;

    MessageManager m_requestMessageManager;

};


template<typename ParamType, typename ReturnType>
RPCRequestInfoPtr TinyRPCClient::async_call(std::string service_name, std::string method_name, ParamType& param){
    ReturnType* resp = new ReturnType;

    auto it_service = m_serviceMap.find(service_name);
    auto it_method = it_service->second->m_methodMap.find(method_name);
    
    google::protobuf::Service * service = it_service->second->m_service;
    const google::protobuf::MethodDescriptor * method = it_method->second;

    int msg_no = m_requestMessageManager.getGlobalMessageId();
    RPCRequestInfoPtr info = std::make_shared<RPCRequestInfo>(resp);
    m_requestMessageManager.set(msg_no, info);

    auto func = [&](){
        service->CallMethod(method, nullptr, &param, resp, (google::protobuf::Closure *)&m_requestMessageManager[msg_no]);
    };
    
    Task task = Task(func); 
    // task runinloop
    getNextLoop()->runInLoop(task);

    return m_requestMessageManager.getRequestInfo(msg_no);
}


template<typename ParamType, typename ReturnType>
ReturnType TinyRPCClient::call(std::string service_name, std::string method_name, ParamType& param){
    ReturnType resp ;

    auto it_service = m_serviceMap.find(service_name);
    auto it_method = it_service->second->m_methodMap.find(method_name);
    
    google::protobuf::Service * service = it_service->second->m_service;
    const google::protobuf::MethodDescriptor * method = it_method->second;

    uint32_t msg_no = m_requestMessageManager.getGlobalMessageId();
    RPCRequestInfoPtr info = std::make_shared<RPCRequestInfo>(&resp);
    info->setNo(msg_no);
    m_requestMessageManager.set(msg_no, info);
    info->setStatus(SEND);
    info->increaseTryTime();
    auto co_func = [&](){
        service->CallMethod(method, nullptr, &param, &resp, (google::protobuf::Closure *)&m_requestMessageManager[msg_no]);
    };
    
    Task task = Task(co_func, true); 
    // std::cout<<"call runInLoop"<<std::endl;
    //LOG_INFO<<"call runInLoop";
    // task runinloop
    getNextLoop()->runInLoop(task);
    while(true){
        if(!info->isSuccess() && info->getTryTimes() < 5){
            info->setStatus(SEND);
            info->increaseTryTime();
            Task task = Task(co_func, true); 
            getNextLoop()->runInLoop(task);
        }else{
            break;
        }
    }
    //info->isSuccess();
    m_requestMessageManager.deleteRequestInfoPtr(msg_no);
    return resp;
}

}